本节代码对应 GitHub 分支: chapter6

仓库传送门 (opens new window)

# axios 请求准备

在 api/request.js 中加入:

export const getAlbumDetailRequest = id => {
  return axiosInstance.get (`/playlist/detail?id=${id}`);
};

# redux 层开发

# 1. 声明初始化 state

//reducer.js
import * as actionTypes from './constants';
import { fromJS } from 'immutable';

const defaultState = fromJS ({
  currentAlbum: {},
  enterLoading: false,
})

# 2. 定义 constants

//constants.js
export const CHANGE_CURRENT_ALBUM = 'album/CHANGE_CURRENT_ALBUM';
export const CHANGE_ENTER_LOADING = 'album/CHANGE_ENTER_LOADING';

# 3. 定义 reducer 函数

//reducer.js
export default (state = defaultState, action) => {
  switch (action.type) {
    case actionTypes.CHANGE_CURRENT_ALBUM:
      return state.set ('currentAlbum', action.data);
    case actionTypes.CHANGE_ENTER_LOADING:
      return state.set ('enterLoading', action.data);
    default:
      return state;
  }
};

# 4. 编写具体的 action

//actionCreators.js
import { CHANGE_CURRENT_ALBUM, CHANGE_ENTER_LOADING } from './constants';
import { getAlbumDetailRequest } from '../../../api/request';
import { fromJS } from 'immutable';

const changeCurrentAlbum = (data) => ({
  type: CHANGE_CURRENT_ALBUM,
  data: fromJS (data)
});

export const changeEnterLoading = (data) => ({
  type: CHANGE_ENTER_LOADING,
  data
});

export const getAlbumList = (id) => {
  return dispatch => {
    getAlbumDetailRequest (id).then (res => {
      let data = res.playlist;
      dispatch (changeCurrentAlbum (data));
      dispatch (changeEnterLoading (false));
    }).catch (() => {
      console.log ("获取 album 数据失败!")
    });
  }
};

# 5. 将相关变量导出

//index.js
import reducer from './reducer'
import * as actionCreators from './actionCreators'

export { reducer, actionCreators };

# 组件连接 Redux

首先,需要将 Album 下的 reducer 注册到全局 store,在 src 目录下的 store/reducer.js 中,内容如下:

import { combineReducers } from 'redux-immutable';
import { reducer as recommendReducer } from '../application/Recommend/store/index';
import { reducer as singersReducer } from '../application/Singers/store/index';
import { reducer as rankReducer } from '../application/Rank/store/index';
import { reducer as albumReducer } from '../application/Album/store/index';

export default combineReducers ({
  recommend: recommendReducer,
  singers: singersReducer ,
  rank: rankReducer,
  album: albumReducer
});

现在在 Album/index.js 中,准备连接 Redux。 增加代码:

import { connect } from 'react-redux';

// 组件代码

// 映射 Redux 全局的 state 到组件的 props 上
const mapStateToProps = (state) => ({
  currentAlbum: state.getIn (['album', 'currentAlbum']),
  enterLoading: state.getIn (['album', 'enterLoading']),
});
// 映射 dispatch 到 props 上
const mapDispatchToProps = (dispatch) => {
  return {
    getAlbumDataDispatch (id) {
      dispatch (changeEnterLoading (true));
      dispatch (getAlbumList (id));
    },
  }
};

// 将 ui 组件包装成容器组件
export default connect (mapStateToProps, mapDispatchToProps)(React.memo (Album));

# 组件对接真实数据

在组件代码中,

import React, {useState, useCallback, useRef, useEffect} from 'react';
import { getAlbumList, changeEnterLoading } from './store/actionCreators';

// 从路由中拿到歌单的 id
const id = props.match.params.id;

const { currentAlbum:currentAlbumImmutable, enterLoading } = props;
const { getAlbumDataDispatch } = props;

useEffect (() => {
  getAlbumDataDispatch (id);
}, [getAlbumDataDispatch, id]);

// 同时将 mock 数据的代码删除
let currentAlbum = currentAlbumImmutable.toJS ();

但是如果你现在进入页面,会报一个错误: cannot read avatarUrl of undefined

这是为什么呢?

当页面进入 Ajax 请求还没有获取数据时,currentAlbum 的值为初始态 {}。直到数据异步加载完成,currentAlbum 才会改变,那么在这个过程中,通过 currentAlbum.creator 为 undefined,通过 current.creator.avatarUrl 取值自然会报错。这样的问题在日常开发中非常常见,那怎么避免这个问题?

我们需要在渲染前做一个非空对象的判断。

首先在 api/utils 中写一个工具函数:

// 判断一个对象是否为空
export const isEmptyObject = obj => !obj || Object.keys (obj).length === 0;

然后自行导入到 Album 组件中。

组件中修改如下:

{
  !isEmptyObject (currentAlbum) ? (
    <Scroll
      onScroll={handleScroll}
      bounceTop={false}
    >
    // 省略内部代码
    </Scroll>
  ) : null
}

这样页面就能正常显示啦。

# 进场 Loaing 动画添加

import Loading from '../../baseUI/loading/index';

// 在 Container 样式组件中添加
{ enterLoading ? <Loading></Loading> : null}

到此为止,UI 和数据已经打通了,接下来我们来做一些优化。

阅读全文